package com.github.common.extend.demo.config.desensitization.load;

import com.github.common.extend.demo.config.desensitization.DesensitizationConfigException;
import com.github.common.extend.demo.config.desensitization.OriginalValQueryParam;
import com.github.common.extend.demo.config.desensitization.listener.DesensitizationListener;
import com.github.common.extend.demo.config.desensitization.meta.MenuMeta;
import com.github.common.extend.demo.config.desensitization.meta.PropertyMeta;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author wangjj7
 * @date 2022/8/1
 * @description
 */
@Slf4j
@Component
public class DefaultMenuLoadManager implements MenuLoadManager {

    @Autowired(required = false)
    private List<MenuLoader> menuLoaders = Collections.EMPTY_LIST;

    @Autowired(required = false)
    private List<DesensitizationListener> listeners = Collections.EMPTY_LIST;

    private long lastedCacheTimestamp = 0L;

    private long cacheDurationMS = 30L * 60 * 1000;

    private List<MenuMeta> cacheMenuMetas = Collections.EMPTY_LIST;

    @Override
    public MenuMeta loadMenu(String nameSpace ,String menuCode) {
        if (StringUtils.isBlank(menuCode)){
            return null;
        }
        return loadMenus().stream()
                .filter(menuMeta -> StringUtils.equals(nameSpace ,menuMeta.getNamespace())
                        && StringUtils.equals(menuMeta.getCode() ,menuCode))
                .findFirst()
                .orElse(null);
    }

    @Override
    public List<MenuMeta> loadMenus()
    {
        if (!isExpired()){
            return cacheMenuMetas;
        }

        synchronized (this) {
            if (isExpired()) {
                log.info("脱敏菜单已过期，重新加载");
                List<MenuMeta> menuMetas = menuLoaders.stream()
                        .map(MenuLoader::menuMetas)
                        .flatMap(list -> list.stream())
                        .collect(Collectors.toList());

                validate(menuMetas);
                cacheMenuMetas = menuMetas;
                lastedCacheTimestamp = System.currentTimeMillis();
            }
        }

        return cacheMenuMetas;
    }

    @Override
    public String queryOriginalVal(OriginalValQueryParam param)
    {
        if (StringUtils.isEmpty(param.getMenuCode())
                || StringUtils.isEmpty(param.getPropertyCode())
                || StringUtils.isEmpty(param.getUniqueKeyValue())){
            throw new IllegalArgumentException("缺少必要参数");
        }

        String uniqueKeyValue = param.getUniqueKeyValue();

        MenuMeta<PropertyMeta> menuMeta = loadMenu(param.getNamespace() ,param.getMenuCode());
        if (menuMeta != null) {
            for (PropertyMeta desensitizationProperty : menuMeta.getProperties()) {
                if (desensitizationProperty.getCode().equals(param.getPropertyCode())) {
                    beforeDesensitization(param ,menuMeta ,desensitizationProperty);
                    String result = desensitizationProperty.getFunction().apply(uniqueKeyValue);
                    afterDesensitization(param ,result ,menuMeta ,desensitizationProperty);
                    return result;
                }
            }
        }

        throw new DesensitizationConfigException(String.format("【%s】获取脱敏函数失败" ,param));
    }

    /*
     * 脱敏前执行
     */
    private void beforeDesensitization(OriginalValQueryParam param ,
                                       MenuMeta menuMeta ,
                                       PropertyMeta propertyMeta)
    {
        for (DesensitizationListener desensitizationListener : listeners)
        {
            desensitizationListener.before(param, menuMeta, propertyMeta);
        }
    }

    /*
     * 脱敏后执行
     */
    private void afterDesensitization(OriginalValQueryParam param,
                                      String desensitizationVal,
                                      MenuMeta menuMeta,
                                      PropertyMeta propertyMeta)
    {
        for (DesensitizationListener desensitizationListener : listeners) {
            desensitizationListener.after(param, desensitizationVal, menuMeta, propertyMeta);
        }
    }

    private void validate(List<MenuMeta> menuMetas)
    {
        for (MenuMeta menuMeta : menuMetas) {
            String menuCode = menuMeta.getCode();
            String namespace = menuMeta.getNamespace();
            if ((StringUtils.isNotBlank(menuCode) && menuCode.contains(MenuMeta.INTERVAL))
                    || (StringUtils.isNotBlank(namespace) && namespace.contains(MenuMeta.INTERVAL))){
                throw new DesensitizationConfigException(String.format("命名空间.菜单编码不能包含特殊字符【%s】" ,MenuMeta.INTERVAL));
            }
        }

        String repeatMenuCodes = menuMetas.stream()
                .collect(Collectors.groupingBy(MenuMeta::key, Collectors.counting()))
                .entrySet()
                .stream()
                .filter(entry -> entry.getValue() > 1)
                .map(Map.Entry::getKey)
                .collect(Collectors.joining(","));

        if (StringUtils.isNotEmpty(repeatMenuCodes)){
            throw new DesensitizationConfigException(String.format("【命名空间.菜单编码】重复,【%s】" ,repeatMenuCodes));
        }

        for (MenuMeta menuMeta : menuMetas)
        {
            List<PropertyMeta> desensitizationProperties = menuMeta.getProperties();
            if (CollectionUtils.isNotEmpty(desensitizationProperties)){
                String repeatPropertyCodes = desensitizationProperties.stream()
                        .collect(Collectors.groupingBy(PropertyMeta::getCode, Collectors.counting()))
                        .entrySet()
                        .stream()
                        .filter(entry -> entry.getValue() > 1)
                        .map(Map.Entry::getKey)
                        .collect(Collectors.joining(","));

                if (StringUtils.isNotEmpty(repeatMenuCodes)){
                    throw new DesensitizationConfigException(
                            String.format("菜单【%s】脱敏属性编码重复，【%s】" ,menuMeta.getCode() ,repeatPropertyCodes)
                    );
                }
            }
        }
    }

    private boolean isExpired(){
        return System.currentTimeMillis() - lastedCacheTimestamp > cacheDurationMS;
    }

}
